const {
    parseMarkdown
} = require('./markdown_tool');
var path = require('path');
var os = require('os');
var open = require('open');
var fs = require('fs'),
    path = require('path'),
    normalize = path.normalize,
    sep = path.sep,
    extname = path.extname,
    join = path.join;
var express = require('express');
var serveIndex = require('serve-index');
var Batch = require('batch');
var mime = require("mime-types");
var escapeHtml = require('escape-html');
const {
    formatBytes,
    formatDate
} = require('./format_tool');

let _downloadable = false;

module.exports = function (app, localIP, original_dir, original_file, http_port, disableServeIndex, urlPattern, ipPattern, downloadable = false) {
    _downloadable = downloadable;
    const log4js = require('log4js');
    const logger = log4js.getLogger();
    const logger_debugger = log4js.getLogger("debugger");
    logger.debug('Startup staticserver');

    //简单判断是在mac osx或者在windows中
    var isMac = __dirname.indexOf('/') == 0;
    var urlMap = {
        '/': original_dir, // 原始目录为根目录
        '/api/public/': path.join(__dirname, "../../public"),
    };
    console.log(`urlPattern=${urlPattern}`);
    console.log("[Route]\t\t\t[Path]");
    console.log("-------\t\t\t------");

    var trustIps = [
        "127.0.0.1",
        "localhost",
    ];
    var driversRegistered = [];
    var macRegistered = false;
    //hook处理：被web构建后的文件
    app.use(function (req, res, next) {
        if (urlPattern) {
            if (!new RegExp(urlPattern).test(req.url)) {
                res.writeHead(403);
                res.end(`${req.url} access denied~`);
                return;
            }
        }

        var isTrusted;
        // isTrusted = !trustIps.every(function (ip) {
        //     return req.ip.indexOf(ip) == -1;
        // });
        // 全部可信
        isTrusted = !ipPattern || new RegExp(ipPattern).test(req.ip);
        if (!isTrusted) {
            res.writeHead(403);
            res.end(`${req.ip} access denied~`);
            return;
        }
        //要排除/swf_modules目录,排除lv目录
        // if(req.url.indexOf("/static/flash") != -1 && req.url.indexOf("/static/flash/swf_modules") == -1 && req.url.indexOf("/static/flash/lv") == -1)
        // {
        //     req.url = req.url.replace(/-[0-9a-zA-Z_]+\./, ".");
        //     // console.log("Redirect to %s", req.url)
        // }
        // var logReg = /\/(trace|debug|info|warn|error|fatal)\//;
        // var logResult = req.url.match(logReg);
        // if (logResult && logResult.index == 0) {
        //     var logType = logResult[1];
        //     var logData = decodeURI(req.url.substr(logResult[0].length));
        //     logger_debugger[logType](logData);
        //     // next(200, `{code: 0, data: "${logData}"}`);
        //     // next();
        //     res.status(200).send(`{"code": 0, "data": "${encodeURI(logData)}"}`);
        //     return;
        // }

        var stopServerReg = /^\/kill/;
        if (req.url.match(stopServerReg)) {
            res.status(200).send(`{"code": 0, "data": "Kill server successfully!"}`);
            logger.warn("Kill server successfully! from " + req.ip);
            setTimeout(() => process.exit(0), 10);
            return;
        }

        // 文件查看
        let driverReg = !isMac ? /^\/(?:file|view)\/([a-zA-Z]+)(\:||%3A)?\// : /^\/(?:file|view)\//;
        var driverResult = req.url.match(driverReg);
        if (driverResult) { // 有驱动盘符标识
            if (!isMac) {
                var driverName = driverResult[1];
                if (driversRegistered.indexOf(driverName) == -1) {
                    driversRegistered.push(driverName);
                    registerRoute(`/file/${driverName}`, driverName + ":\\");
                    registerRoute(`/file/${driverName}:`, driverName + ":\\");
                    registerRoute(`/file/${driverName}%3A`, driverName + ":\\");
                    registerRoute(`/view/${driverName}`, driverName + ":\\");
                    registerRoute(`/view/${driverName}:`, driverName + ":\\");
                    registerRoute(`/view/${driverName}%3A`, driverName + ":\\");
                }
            } else {
                if (!macRegistered) {
                    registerRoute(`/file`, "/");
                    registerRoute(`/view`, "/");
                    macRegistered = true;
                }
            }
        }
        if (downloadable) {
            // 下载
            driverReg = !isMac ? /\/download\/([a-zA-Z]+)(\:||%3A)?\/([^\?]*)/ : /\/download\/([^\?]*)/;
            driverResult = req.url.match(driverReg);
            if (driverResult) {
                let target;
                if (!isMac) {
                    target = path.resolve(driverResult[1] + "://", driverResult[3]);
                } else {
                    target = path.resolve("/", driverResult[1]);
                }
                if (fs.statSync(target).isDirectory()) {
                    const AdmZip = require("adm-zip");
                    var zip = new AdmZip();
                    let files = require("glob").sync(target + "/**/*", Object.assign({nodir: true}, req.query));
                    // require("glob")(target + "/**/*", Object.assign({nodir: true}, req.query), (err, files) => {
                    files.forEach(file => {
                        zip.addFile(path.relative(target, file), fs.readFileSync(file));
                    });
                    res
                    // .set('Content-Disposition', require("content-disposition")("test.zip"))
                    .attachment(`${path.basename(target)}.zip`)
                    // .status(200)
                    .end(zip.toBuffer());
                    // });
                    return;
                } else {
                    res.sendFile(target);
                    return;
                }
            }
        }
        // 如果有强制代理
        if (req.query.PROXY_TO) {
            res.status(200).send(fs.readFileSync("./public/proxy/" + req.query.PROXY_TO, "utf-8"));
            return;
        }
        next();
    });
    for (var url in urlMap) {
        registerRoute(url, urlMap[url]);
    }

    function registerRoute(route, url) {
        //目录访问，无法访问文件，还要指定express.static才能访问
        if (!disableServeIndex) {
            app.use(route, serveIndex(url, {
                icons: true,
                view: "details",
                filter: (filename, index, files, dir) => {
                    let filepath = path.join(dir, filename);
                    // 检查文件访问权限
                    try {
                        let fileStat = fs.statSync(filepath);
                        // 1：execute; 4: read; 2: write
                        // console.log(fileStat.mode, "execute", checkPermission(fileStat.mode, 1), "read", checkPermission(fileStat.mode, 4), "write", checkPermission(fileStat.mode, 2), filepath)
                    } catch (error) {
                        return false;
                    }
                    return true;
                },
                // template: "template.html",
            }));
        }
        var options = {
            setHeaders: function (res, path, stat) {
                res.set("Access-Control-Allow-Origin", "*");
            }
        };
        //文件访问
        app.use(route, express.static(url, options));



        var tabLen = Math.ceil(((8 * 3) - route.length) / 8);
        var tab = "";
        while (tabLen) {
            tab += "\t";
            tabLen--;
        }
        console.log("%s%s%s", route, tab, url);
    }
    console.log("-------\t\t\t------");
    app.use(require('express-mung').write(function (chunk, encoding, req, res) {
        if (req.baseUrl.match(/^\/view\//)) {
            let queryIndex = req.url.indexOf("?");
            let extname = path.extname(queryIndex != -1 ? req.url.substr(0, queryIndex) : req.url).toLowerCase().substr(1);
            switch (extname) {
                case "md": {
                    res.setHeader("content-type", "text/html; charset=utf-8");
                    let body = parseMarkdown(chunk.toString("utf-8"));
                    res.send(body)
                    break;
                }
                default: {
                    res.setHeader("content-type", "text/html; charset=utf-8");
                    let body = parseMarkdown(`\`\`\`${extname}\n` + chunk.toString("utf-8") + "\n```");
                    res.send(body)
                    break;
                }
            }
        }
    }));


    if (!app['__IS_DEBUG__']) {
        // console.log(original_dir, original_file, process.cwd());
        // if (original_dir == process.cwd()) {
        //     open(`http://${localIP || "localhost"}:${http_port}/file/${path.join(os.homedir(), "Desktop")}`);
        // } else {
        open(`http://${localIP || "localhost"}:${http_port}/${original_file.replace(/ /g, "%20")}`);
        // }
    }

    function checkPermission(mode, mask) {
        return !!(mask & parseInt((mode & parseInt("777", 8)).toString(8)[0]));
    }


    process.on('exit', function () {
        logger.warn('Exit staticserver');
    });
};



////////////// serve-index


/**
 * Icon map.
 */

var icons = {
    // base icons
    'default': 'page_white.png',
    'folder': 'folder.png',

    // generic mime type icons
    'image': 'image.png',
    'text': 'page_white_text.png',
    'video': 'film.png',

    // generic mime suffix icons
    '+json': 'page_white_code.png',
    '+xml': 'page_white_code.png',
    '+zip': 'box.png',

    // specific mime type icons
    'application/font-woff': 'font.png',
    'application/javascript': 'page_white_code_red.png',
    'application/json': 'page_white_code.png',
    'application/msword': 'page_white_word.png',
    'application/pdf': 'page_white_acrobat.png',
    'application/postscript': 'page_white_vector.png',
    'application/rtf': 'page_white_word.png',
    'application/vnd.ms-excel': 'page_white_excel.png',
    'application/vnd.ms-powerpoint': 'page_white_powerpoint.png',
    'application/vnd.oasis.opendocument.presentation': 'page_white_powerpoint.png',
    'application/vnd.oasis.opendocument.spreadsheet': 'page_white_excel.png',
    'application/vnd.oasis.opendocument.text': 'page_white_word.png',
    'application/x-7z-compressed': 'box.png',
    'application/x-sh': 'application_xp_terminal.png',
    'application/x-font-ttf': 'font.png',
    'application/x-msaccess': 'page_white_database.png',
    'application/x-shockwave-flash': 'page_white_flash.png',
    'application/x-sql': 'page_white_database.png',
    'application/x-tar': 'box.png',
    'application/x-xz': 'box.png',
    'application/xml': 'page_white_code.png',
    'application/zip': 'box.png',
    'image/svg+xml': 'page_white_vector.png',
    'text/css': 'page_white_code.png',
    'text/html': 'page_white_code.png',
    'text/less': 'page_white_code.png',

    // other, extension-specific icons
    '.accdb': 'page_white_database.png',
    '.apk': 'box.png',
    '.app': 'application_xp.png',
    '.as': 'page_white_actionscript.png',
    '.asp': 'page_white_code.png',
    '.aspx': 'page_white_code.png',
    '.bat': 'application_xp_terminal.png',
    '.bz2': 'box.png',
    '.c': 'page_white_c.png',
    '.cab': 'box.png',
    '.cfm': 'page_white_coldfusion.png',
    '.clj': 'page_white_code.png',
    '.cc': 'page_white_cplusplus.png',
    '.cgi': 'application_xp_terminal.png',
    '.cpp': 'page_white_cplusplus.png',
    '.cs': 'page_white_csharp.png',
    '.db': 'page_white_database.png',
    '.dbf': 'page_white_database.png',
    '.deb': 'box.png',
    '.dll': 'page_white_gear.png',
    '.dmg': 'drive.png',
    '.docx': 'page_white_word.png',
    '.erb': 'page_white_ruby.png',
    '.exe': 'application_xp.png',
    '.fnt': 'font.png',
    '.gam': 'controller.png',
    '.gz': 'box.png',
    '.h': 'page_white_h.png',
    '.ini': 'page_white_gear.png',
    '.iso': 'cd.png',
    '.jar': 'box.png',
    '.java': 'page_white_cup.png',
    '.jsp': 'page_white_cup.png',
    '.lua': 'page_white_code.png',
    '.lz': 'box.png',
    '.lzma': 'box.png',
    '.m': 'page_white_code.png',
    '.map': 'map.png',
    '.msi': 'box.png',
    '.mv4': 'film.png',
    '.otf': 'font.png',
    '.pdb': 'page_white_database.png',
    '.php': 'page_white_php.png',
    '.pl': 'page_white_code.png',
    '.pkg': 'box.png',
    '.pptx': 'page_white_powerpoint.png',
    '.psd': 'page_white_picture.png',
    '.py': 'page_white_code.png',
    '.rar': 'box.png',
    '.rb': 'page_white_ruby.png',
    '.rm': 'film.png',
    '.rom': 'controller.png',
    '.rpm': 'box.png',
    '.sass': 'page_white_code.png',
    '.sav': 'controller.png',
    '.scss': 'page_white_code.png',
    '.srt': 'page_white_text.png',
    '.tbz2': 'box.png',
    '.tgz': 'box.png',
    '.tlz': 'box.png',
    '.vb': 'page_white_code.png',
    '.vbs': 'page_white_code.png',
    '.xcf': 'page_white_picture.png',
    '.xlsx': 'page_white_excel.png',
    '.yaws': 'page_white_code.png'
};

/**
 * Sort function for with directories first.
 */

function fileSort(sortType, a, b) {
    // sort ".." to the top
    if (a.name === '..' || b.name === '..') {
        return a.name === b.name ? 0 :
            a.name === '..' ? -1 : 1;
    }
    switch (sortType) {
        case "size_desc": {
            return Number(b.stat && b.stat.isDirectory()) - Number(a.stat && a.stat.isDirectory()) ||
                b.stat.size - a.stat.size ||
                String(a.name).toLocaleLowerCase().localeCompare(String(b.name).toLocaleLowerCase());
        }
        case "size_asc": {
            return Number(b.stat && b.stat.isDirectory()) - Number(a.stat && a.stat.isDirectory()) ||
                a.stat.size - b.stat.size ||
                String(a.name).toLocaleLowerCase().localeCompare(String(b.name).toLocaleLowerCase());
        }
        case "date_desc": {
            return Number(b.stat && b.stat.isDirectory()) - Number(a.stat && a.stat.isDirectory()) ||
                b.stat.mtimeMs - a.stat.mtimeMs ||
                String(a.name).toLocaleLowerCase().localeCompare(String(b.name).toLocaleLowerCase());
        }
        case "date_asc": {
            return Number(b.stat && b.stat.isDirectory()) - Number(a.stat && a.stat.isDirectory()) ||
                a.stat.mtimeMs - b.stat.mtimeMs ||
                String(a.name).toLocaleLowerCase().localeCompare(String(b.name).toLocaleLowerCase());
        }
        case "name_desc": {
            return Number(b.stat && b.stat.isDirectory()) - Number(a.stat && a.stat.isDirectory()) ||
                String(a.name).toLocaleLowerCase().localeCompare(String(b.name).toLocaleLowerCase()) * -1;
        }
    }
    return Number(b.stat && b.stat.isDirectory()) - Number(a.stat && a.stat.isDirectory()) ||
        String(a.name).toLocaleLowerCase().localeCompare(String(b.name).toLocaleLowerCase());
}


/**
 * Stat all files and return array of stat
 * in same order.
 */

function stat(dir, files, cb) {
    var batch = new Batch();

    batch.concurrency(10);

    files.forEach(function (file) {
        batch.push(function (done) {
            fs.stat(join(dir, file), function (err, stat) {
                if (err && err.code !== 'ENOENT') return done(err);

                // pass ENOENT as null stat, not error
                done(null, stat || null);
            });
        });
    });

    batch.end(cb);
}

/*!
 * Icon cache.
 */

var cache = {};
/**
 * Load and cache the given `icon`.
 *
 * @param {String} icon
 * @return {String}
 * @api private
 */

function load(icon) {
    if (cache[icon]) return cache[icon];
    // check location of serve-index
    var locations = [
        path.join(__dirname, '../../node_modules/serve-index'),
        path.join(__dirname, '../../../serve-index'),
    ];
    var location = locations.find(function (location) {
        return fs.existsSync(location);
    });
    return cache[icon] = fs.readFileSync(path.join(location, '/public/icons/' + icon), 'base64');
}

/**
 * Get the icon data for the file name.
 */

function iconLookup(filename) {
    var ext = extname(filename);

    // try by extension
    if (icons[ext]) {
        return {
            className: 'icon-' + ext.substring(1),
            fileName: icons[ext]
        };
    }

    var mimetype = mime.lookup(ext);

    // default if no mime type
    if (mimetype === false) {
        return {
            className: 'icon-default',
            fileName: icons.default
        };
    }

    // try by mime type
    if (icons[mimetype]) {
        return {
            className: 'icon-' + mimetype.replace('/', '-'),
            fileName: icons[mimetype]
        };
    }

    var suffix = mimetype.split('+')[1];

    if (suffix && icons['+' + suffix]) {
        return {
            className: 'icon-' + suffix,
            fileName: icons['+' + suffix]
        };
    }

    var type = mimetype.split('/')[0];

    // try by type only
    if (icons[type]) {
        return {
            className: 'icon-' + type,
            fileName: icons[type]
        };
    }

    return {
        className: 'icon-default',
        fileName: icons.default
    };
}
/**
 * Load icon images, return css string.
 */

function iconStyle(files, useIcons) {
    if (!useIcons) return '';
    var i;
    var list = [];
    var rules = {};
    var selector;
    var selectors = {};
    var style = '';

    for (i = 0; i < files.length; i++) {
        var file = files[i];

        var isDir = file.stat && file.stat.isDirectory();
        var icon = isDir ? {
                className: 'icon-directory',
                fileName: icons.folder
            } :
            iconLookup(file.name);
        var iconName = icon.fileName;

        selector = '#files .' + icon.className + ' .name';

        if (!rules[iconName]) {
            rules[iconName] = 'background-image: url(data:image/png;base64,' + load(iconName) + ');'
            selectors[iconName] = [];
            list.push(iconName);
        }

        if (selectors[iconName].indexOf(selector) === -1) {
            selectors[iconName].push(selector);
        }
    }

    for (i = 0; i < list.length; i++) {
        iconName = list[i];
        style += selectors[iconName].join(',\n') + ' {\n  ' + rules[iconName] + '\n}\n';
    }

    return style;
}
/**
 * Send a response.
 * @private
 */

function send(res, type, body) {
    // security header for content sniffing
    res.setHeader('X-Content-Type-Options', 'nosniff')

    // standard headers
    res.setHeader('Content-Type', type + '; charset=utf-8')
    res.setHeader('Content-Length', Buffer.byteLength(body, 'utf8'))

    // body
    res.end(body, 'utf8')
}
/**
 * Map html `dir`, returning a linked path.
 */

function htmlPath(dir) {
    var parts = dir.split('/');
    var crumb = new Array(parts.length);

    for (var i = 0; i < parts.length; i++) {
        var part = parts[i];

        if (part) {
            parts[i] = encodeURIComponent(part);
            crumb[i] = '<a href="' + escapeHtml(parts.slice(0, i + 1).join('/')) + '">' + escapeHtml(part) + '</a>';
        }
    }

    return crumb.join(' / ');
}

const detailHeaders = [{
        cls: 'name',
        title: '名称'
    },
    {
        cls: 'size',
        title: '大小'
    },
    {
        cls: 'date',
        title: '修改日期'
    },
];
const detailHeaderSortKeys = detailHeaders.map(v => v.cls);
const detailHeaderSortMethods = ['asc', 'desc'];
const detailHeaderSortMethodChars = [' ▲', ' ▼'];

/**
 * Map html `files`, returning an html unordered list.
 * @private
 */
function createHtmlFileList(files, dir, useIcons, view, req) {
    let sortType = req.query.sort || 'name_asc';
    var html = '<ul id="files" class="view-' + escapeHtml(view) + '">';
    if (view == 'details') {
        sorts = sortType.split("_");
        let sortKey = sorts[0];
        let sortMethod = sorts[1];
        if (!detailHeaderSortKeys.includes(sortKey)) {
            sortKey = "name";
        }
        if (!detailHeaderSortMethods.includes(sortMethod)) {
            sortMethod = "asc";
        }
        let sortMehodIndex = detailHeaderSortMethods.indexOf(sortMethod);
        html += '<li class="header">';
        html += detailHeaders.map(v => {
            return `<span class="${v.cls}"><a href="?sort=${v.cls}_${detailHeaderSortMethods[v.cls == sortKey ? 1 - sortMehodIndex : 0]}">${v.title}${v.cls == sortKey ? detailHeaderSortMethodChars[sortMehodIndex] : ""}</a></span>`;
        }).join("");
        html += '</li>';
    }

    html += files.map(function (file) {
        var classes = [];
        var isDir = file.stat && file.stat.isDirectory();
        var path = dir.split('/').map(function (c) {
            return encodeURIComponent(c);
        });

        if (useIcons) {
            classes.push('icon');

            if (isDir) {
                classes.push('icon-directory');
            } else {
                var ext = extname(file.name);
                var icon = iconLookup(file.name);

                classes.push('icon');
                classes.push('icon-' + ext.substring(1));

                if (classes.indexOf(icon.className) === -1) {
                    classes.push(icon.className);
                }
            }
        }

        path.push(encodeURIComponent(file.name));

        var date = file.stat && file.name !== '..' ?
            formatDate(file.stat.mtime, "hh:mm:ss YYYY/MM/DD") :
            '';
        var size = file.stat && !isDir ?
            formatBytes(file.stat.size) :
            '';

        return '<li><a href="' +
            escapeHtml(normalizeSlashes(normalize(path.join('/')))) +
            '" class="' + escapeHtml(classes.join(' ')) + '"' +
            ' title="' + escapeHtml(file.name) + '">' +
            '<span class="name">' + escapeHtml(file.name) + '</span>' +
            '<span class="size">' + escapeHtml(size) + '</span>' +
            '<span class="date">' + escapeHtml(date) + '</span>' +
            '</a></li>';
    }).join('\n');

    html += '</ul>';

    return html;
}

/**
 * Normalizes the path separator from system separator
 * to URL separator, aka `/`.
 *
 * @param {String} path
 * @return {String}
 * @api private
 */

function normalizeSlashes(path) {
    return path.split(sep).join('/');
};
/**
 * Create function to render html.
 */

function createHtmlRender(template, req) {
    return function render(locals, callback) {
        // read template
        fs.readFile(template, 'utf8', function (err, str) {
            if (err) return callback(err);
            let downloadable = _downloadable && (locals.directory.indexOf("/view/") == 0 || locals.directory.indexOf("/file/") == 0);
            var body = str
                .replace(/\{style\}/g, locals.style.concat(iconStyle(locals.fileList, locals.displayIcons)))
                .replace(/\{files\}/g, createHtmlFileList(locals.fileList, locals.directory, locals.displayIcons, locals.viewName, req))
                .replace(/\{directory\}/g, escapeHtml(locals.directory))
                .replace(/\{linked-path\}/g, htmlPath(locals.directory) + (downloadable ? ` - <button><a href="${locals.directory.replace(/^\/(file|view)\//, "/download/")}">打包下载</a></button>` : ""));
            callback(null, body);
        });
    };
}

serveIndex.html = function _html(req, res, files, next, dir, showUp, icons, path, view, template, stylesheet) {
    var render = typeof template !== 'function' ?
        createHtmlRender(template, req) :
        template

    if (showUp) {
        files.unshift('..');
    }

    // stat all files
    stat(path, files, function (err, stats) {
        if (err) return next(err);

        // combine the stats into the file list
        var fileList = files.map(function (file, i) {
            return {
                name: file,
                stat: stats[i]
            };
        });

        // sort file list
        fileList.sort(fileSort.bind(this, req.query.sort));

        // read stylesheet
        fs.readFile(stylesheet, 'utf8', function (err, style) {
            if (err) return next(err);

            style += `
            ul#files li span a {
                color: inherit;
                display: inline;
            }
            
            `;

            // create locals for rendering
            var locals = {
                directory: dir,
                displayIcons: Boolean(icons),
                fileList: fileList,
                path: path,
                style: style,
                viewName: view
            };

            // render html
            render(locals, function (err, body) {
                if (err) return next(err);
                send(res, 'text/html', body)
            });
        });
    });
};
